Skip to content

feat: multi sync example#1622

Open
Viktor-Kalashnykov-da wants to merge 80 commits into
mainfrom
wiktor/multisync-example
Open

feat: multi sync example#1622
Viktor-Kalashnykov-da wants to merge 80 commits into
mainfrom
wiktor/multisync-example

Conversation

@Viktor-Kalashnykov-da

@Viktor-Kalashnykov-da Viktor-Kalashnykov-da commented Apr 14, 2026

Copy link
Copy Markdown

Multi-Synchronizer DvP Example - On ledger API - Part 1

Showcases automatic reassignment of Token from private synchronizer to global one, used on a realistic trade scenario with use of a custom Token.

New test:
https://github.com/canton-network/wallet/pull/1622/changes#diff-37db720ea457076fe6d04f6938d19c44e5dde3d7e1a5815953ff6b83199a1052

Design principles:

  1. !!! We remove handling of synchronizers inside wallet-sdk.
    Client code should be responsible of selecting synchronizerId in multi-sync scenarios.
    If not provided - synchronizer choice is left for canton participant logic.
    Existing tests are updated (refactor(wallet-sdk): remove default synchronizer auto-selection #1740).

  2. We try to show "realistic scenario" - we use multiple parties (Alice, Bob, TokenAdmin, TradingApp) we also
    distribute dars to selected synchronizers (private dars to private). This contributes to relative complexity of the test code

image

source:
https://docs.google.com/presentation/d/1q6LpHi-wC_MO_mzf7wp5D-15j4lNeKjCMW5xmBggaTY

  1. Example works with Token Standard V1
    We have experimented with token standard V2 -> but as V2 is not yet merged to splice use of V2 means even more code

  2. All tests run on multi-sync

This is a first PR in a series
Showcases only on-ledger part - and ensures code works.
There are 2 follow up PRs (wip)

Technical limitations:
In this PR we introduce separate tests for multi-sync (otherwise regular tests are flaky on multi-sync) - that split will be removed in a separate PR (above)

New example script:
docs/wallet-integration-guide/examples/scripts/15-multi-sync-trade.ts

Notes

  1. explicit reassignment
    we experimented with automatic reassignment of contracts - and it basically worked BUT.

It worked as long as Bob is owner of TokenRules contract which seems unrealistic.
We introduced TokenAdmin as additional party that is issuer of Token on app-synchronizer (private one).
But this in fact forces us to use explicit reassignment.

  1. TestToken on global synchronizer. The original intention seemed to be that TestToken dar is only vetted on private synchronizer.
    But since the settlement actually happens on global - TestToken related models must be also on global synchronizer

@Viktor-Kalashnykov-da Viktor-Kalashnykov-da requested review from a team as code owners April 14, 2026 19:09
@Viktor-Kalashnykov-da Viktor-Kalashnykov-da marked this pull request as draft April 14, 2026 19:09
Comment thread docs/wallet-integration-guide/examples/scripts/multi-sync/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/multi-sync/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/15-multi-sync-trade.ts Outdated
Comment thread sdk/wallet-sdk/src/wallet/sdk.ts Fixed
@Viktor-Kalashnykov-da Viktor-Kalashnykov-da changed the title Multi-Sync Example feat(multisync-example): Multi-Sync Example Apr 17, 2026
Comment thread core/token-standard-service/src/token-standard-service.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/utils/synchronizer.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/utils/synchronizer.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/multi-sync/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/multi-sync/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/multi-sync/15-multi-sync-trade.ts Outdated
Comment thread docs/wallet-integration-guide/examples/scripts/utils/synchronizer.ts Outdated
Comment thread scripts/src/start-localnet.ts Outdated
Comment thread sdk/wallet-sdk/src/wallet/namespace/contract/client.ts Outdated
Comment thread sdk/wallet-sdk/src/wallet/namespace/ledger/dar/client.ts Outdated
Comment thread .gitignore Outdated

@jarekr-da jarekr-da left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made first round or review.
Generally that is a scenario we want.
I posted questions and problems to recheck / fix.

@Viktor-Kalashnykov-da Viktor-Kalashnykov-da changed the title feat(multisync-example): Multi-Sync Example feat: multi sync example Apr 21, 2026
Comment thread core/wallet-test-utils/src/wallet-gateway.ts Outdated
Comment thread .vscode/settings.json Outdated
Comment thread api-specs/ledger-api/3.4.12/openapi.yaml
Comment thread core/ledger-client/src/ledger-client.ts Outdated
@jarekr-da jarekr-da marked this pull request as draft June 8, 2026 13:10
jarekr-da and others added 14 commits June 8, 2026 15:30
* Improvement: unification of single sync and multi sync example for local tests

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* Improvement: added unification of single-sync and multi-sync tests in CI

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* Improvement: removed explicit synchronizer assignment for non-multi sync examples

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* Improvement: removed synchronizer dependency for core functionality

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* Improvement: refactoring

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* Improvement: refactoring

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>

* feat: removed uuid in party hint

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>

* fix(15-multi-sync): poll for OTCTradeProposal across participants

The proposal is created on Alice's participant via executeAndWait but read
from Bob's and TradingApp's participants. Cross-participant propagation over
the synchronizer is eventually consistent, so the unified concurrent example
runner exposed a read-after-write race (Required contract not found). Poll the
ACS until the proposal becomes visible instead of reading once.

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>

* feat: removed not needed change

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>

* tech: removed empty file

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>

---------

Signed-off-by: vkalashnykov <viktor.kalashnykov@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Co-authored-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
* fix: eliminate copying of dars from global to app synchronizer

Signed-off-by: Marcin Ziolek <marcin.ziolek@digitalasset.com>

* fix: elmininate the need for custom bootstrap script completely

Signed-off-by: Marcin Ziolek <marcin.ziolek@digitalasset.com>

---------

Signed-off-by: Marcin Ziolek <marcin.ziolek@digitalasset.com>
…s TestToken before app-synchronizer self-transfer in run-15

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
@jarekr-da jarekr-da marked this pull request as ready for review June 10, 2026 10:37
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
…sync flag from run-15 usage

Multi-sync is now the default for start:localnet; --no-multi-sync starts single-synchronizer debug mode.

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
…out test token

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
}
)

const signature = signTransactionHash(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why we've split up the prepare/sign/execute functions is to enable offline signing. Could we move the synchronizerIds as part of the CreatePartyOptions so that on the sdk.party.external.create call this generates all of the topology transactions to be signed and then the SignedpartyCreationService takes in an array of ExecuteOptions?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or disregard this if offline signing is supposed to be enabled in a separate phase or not supported at all as per this

'Cannot register party on additional synchronizer: publicKey and privateKey must be provided (offline signing is not supported for additionalSynchronizerIds)'

* `KNOWN_PACKAGE_VERSION`. Since the already-vetted package is resolved by
* package-name at command-submission time, it is safe to reuse it and continue.
*/
async function vetPackageIdempotent(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be added to the dar namespace in the sdk?

* @param darBytes - Raw DAR file bytes.
* @param synchronizerId - The synchronizer on which the package should be vetted.
*/
export async function vetDar(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this isn't part of the dar namespace where it can be called with sdk.ledger.dar.vet and then we don't have to pass the provider in as an arg (it will just reuse the provider initialized in the sdk) ?

const events = unassignResponse.reassignment?.events ?? []
const unassignedEvent = events.find((e) => 'JsUnassignedEvent' in e)
if (!unassignedEvent || !('JsUnassignedEvent' in unassignedEvent)) {
throw new Error(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: can we use this.ctx.error.throw here instead

* @param synchronizerIds - Synchronizers to submit to in parallel
* @param privateKey - Key used to sign each prepared transaction
*/
public async executeOnSynchronizers(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this convenience method, but I do want the name of this to indicate that it's preparing (for all synchronizers), signing and executing. Do you think there could be any value in also having a bulk prepare method (prepareForAllSynchronizers) for the case of offline signing?

Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Signed-off-by: jarekr-da <jaroslaw.ratajski@digitalasset.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants